iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 14
0
Software Development

Kotlin 島深度之旅 30 天系列 第 14

[Day 14] Kotlin Journey - Abstract class & Interface (抽象類別和介面)

  • 分享至 

  • xImage
  •  

Abstract class & Interface (抽象類別和介面)

抽象類別在剛開始學習 OOP 的時候是蠻困擾我的一種設計,但在多看一些 lib 或程式後,其實會慢慢體會到抽象的概念。

古早有人說繼承就是一種 is-a 的關係,介面實作就是 has-a 的關係,但其實對我來說抽象類別和介面都是定義規範的一種手段,用來描述將來我的程式應該有什麼屬性或方法,這樣想可能簡單一點。

為什麼需要定義這類像規範的東西呢,我想是因為一個複雜的 class 通常會繼承某個抽象類別或實作多個介面,這就是 OOP 的設計概念,也會讓程式更有架構,如果寫什麼東西都嚕再一起的話,那程式的可讀性和維護就會變得很困難了。

但從設計的角度來看,抽象類別通常都是在設計的過程中,發現很多 class 有許多共通之處,才會抽象出一個父類別來做繼承,比較像是從下而上的想法。

比較直觀的反而是介面,因為我們會先定義出介面後,再來做實作,比較像是由上而下的設計,而且介面實做是可以多個的,但繼承不行,繼承也可能會有繼承太多層太深的問題,所以如果要繼承,通常也不建議繼承太深。

abstract class (抽象類別)

抽象類別是不能被實體化的,只能被繼承,要記住!

繼承此抽象類別,都要覆寫抽象類別內所有標明 abstract 的屬性和方法

抽象類別 View

  • 這裡宣告了一個抽象類別 View, 在 class 前面使用 abstract 關鍵字,便能宣告一個抽象類別
  • 下例還宣告了 abstract 屬性和方法, 這些屬性和方法不需要實作內容只需要宣告即可,子類別(子類別不是抽象類別的話)一定要覆寫這些屬性和方法,不然會有錯誤。
abstract class View(val width: Int, val height: Int) {
    // abstract: 只有宣告,沒有實作, 子類別都要複寫這些方法
    abstract val size: Int
    abstract fun draw()
    abstract fun click()
}

這裡我也宣告了一些其他不是 abstract 的屬性和方法,這些屬性和方法如果也要讓子類別覆寫,就要加上 open 關鍵字。

abstract class View(val width: Int, val height: Int) {
...
	// 有實做的, 有 open 可以 override
	open val color = "dark"
	open fun isHidden(): Boolean {
	    return false
	}
...
}

最後是宣告了一個最普通的函數,這樣代表只要是繼承這個 class 都可以直接使用這個函數。

abstract class View(val width: Int, val height: Int) {
....
	fun test() {
	    println("test")
	}
....
}

抽象類別 ViewGroup

接著我又宣告了一個 抽象類別 ViewGroup 來繼承剛剛的 抽象類別 View

ViewGroup又針對了 View 的 abstract 或 open 屬性和方法做了覆寫, 比較特別的是

abstract override fun isHidden(): Boolean

這句覆寫後,又宣告了 abstract 讓子類別可以真的去實作內容,看起來有點不必要,但這裡只是想要解釋可以藉此把某些方法名稱蓋掉又開放出來強制讓子類別去覆寫的辦法。

override fun test() 這個函數不能被覆寫,因為在 View 裡面沒有宣告 open

abstract class ViewGroup(width: Int, height: Int) : View(width, height) {

    override val size = width * height
    override val color = "white"
    override fun click() {
        println("do clicking...")
    }

    abstract override fun isHidden(): Boolean

    // override fun test() // 不能被覆寫,因為不是 open
}

最後我用了一個 App class 來繼承 ViewGroup

一但寫下繼承 : ViewGroup(width, height,編輯器就會很聰明顯示出哪些屬性和方法是一定要覆寫的。

https://ithelp.ithome.com.tw/upload/images/20200923/20129902IpGdWCYazs.png

類別 App 繼承抽象類別

class App(width: Int, height: Int) : ViewGroup(width, height) {
    override fun isHidden(): Boolean {
        return false
    }

    override fun draw() {
        println("drawing....")
    }

    fun printSize() {
        println("size is $size")
    }

}

結果就會如下

fun main() {
    val app = App(100, 200)
    app.draw()
    app.printSize()
    app.click()
    app.test()
}

結果:
drawing....
size is 20000
do clicking...
test

interface (介面)

介面內宣告的屬性和方法預設都是 abstractopen 的,所以都不需要宣告這兩種關鍵字,因為介面生來就是要用來讓別人實做內容的。

OnClickListener 介面

這裡宣告了一個 OnClickListener 介面,完整程式如下

interface OnClickListener {
    val isClick: Boolean

    // val isClick3 = false  // 不能給初值, 要用 get
    var isClick2: Boolean
    get() = false
    set(value) {
        println(value)
    }

    fun onClick()

    fun onClick2() {
        println("OnClickListener click")
    }

    fun onClick3() {
        println("OnClickListener click")
    }
}

val isClick: Boolean 代表一個抽象的屬性

val isClick3 = false 這個解釋了在介面裡面不能給予變數初值,會有下面的錯誤。

https://ithelp.ithome.com.tw/upload/images/20200923/20129902EHIlmqA6LI.png

要給予初值,可以寫自定的 get(),然後還有要注意,因為在介面內屬性都是抽象的,所以不會有 backing field 可以使用。

var isClick2: Boolean
get() = false
set(value) {
    println(value)
}

fun onClick() 是一個抽象方法,實作介面時一定要覆寫,因為沒有實作的內容。

以下兩個方法,也是抽象方法,但是有實做,所以之後實作時這兩個方法可以做覆寫也可以不用覆寫。

    fun onClick2() {
        println("OnClickListener click")
    }

    fun onClick3() {
        println("OnClickListener click")
    }

OnBlurListener 介面

還多宣告了一個 OnBlurListener 介面

interface OnBlurListener {
    fun onBlur()
}

類別 App 實作介面

最後把這兩個介面加到 App class 上面,在 Kotlin 介面和繼承都使用 : 就可以,所以這兩個介面可以繼續加在 ViewGroup 後面

class App(width: Int, height: Int) : ViewGroup(width, height), OnBlurListener, OnClickListener {
....
}

最後這些覆寫了這些方法和屬性,其中 onClick3() 不是強制要覆寫的,因為在介面裡也有實作。

class App(width: Int, height: Int) : ViewGroup(width, height), OnBlurListener, OnClickListener {

    override fun onBlur() {
        println("app onBlur...")
    }

    override fun onClick() {
        println("app onClick...")
    }

    override fun onClick3() {
        println("app onClick3...")
    }

    override val isClick = true
}

最後來看一下結果吧!

fun main() {
    val app = App(100, 200)
    app.draw()
    app.printSize()
    app.click()
    app.test()
    app.onClick()
    app.onClick2()
    app.onBlur()
    app.isClick2 = true
}

結果:
drawing....
size is 20000
do clicking...
test
app onClick...
OnClickListener click
app onClick3...
app onBlur...
true

最後可以看到只有 app.onClick2() 印出了介面裡本來就有實作的內容,其他都印出在 App class 覆寫的內容

app.isClick2 = true 則會印出了介面裡定義的 set() 的 println

app.onClick() // app onClick...
app.onClick2() // 呼叫介面裡本來就有實作的內容 OnClickListener click
app.onClick3() // app onClick3...
app.onBlur() // app onBlur...
app.isClick2 = true // true 印出了 介面裡定義的 set println

今天就講到這裡啦!謝謝大家,我們明天見!

今日練習的程式在這: 請點我


上一篇
[Day 13] Kotlin Journey - Inheritance (繼承)
下一篇
[Day 15] Kotlin Journey - object, companion object
系列文
Kotlin 島深度之旅 30 天31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言